//////////////////////////////////////////////////////////////////////////
//
// This file is a part of APNS provider project.
// Copyright (c) Vladimir Gumenuk, 2013-2014.
//
// PROJECT:         APNS provider
// FILE:            PushNotification.cpp
// AUTHOR:          Vladimir Gumenuk
// DATE:            22-Dec-2013
// MODIFICATIONS:
//
//////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "PushNotification.h"

static DWORD g_dwNextNotificationID = 0;

HRESULT __stdcall CreatePushNotification( IPushNotification **ppOut )
{
    return CPushNotification::Create( ppOut );
}

HRESULT CPushNotification::Create( IPushNotification **ppOut )
{
    if( !ppOut )
        return E_POINTER;

    typedef CComObject<CPushNotification> TPushNotificationImpl;
    TPushNotificationImpl *p = NULL;
    HRESULT hr = TPushNotificationImpl::CreateInstance( &p );
    if( SUCCEEDED( hr ) )
    {
        p->AddRef();

        hr = p->QueryInterface( IID_IPushNotification, (void**)ppOut );

        p->Release();
    }

    return hr;
}

CPushNotification::CPushNotification()
{
    m_dwID = 0;
    m_dwExpiration = 0;
    m_bPriority = 0;
    m_lpszPayload = NULL;
}

CPushNotification::~CPushNotification()
{
    if( m_lpszPayload )
    {
        free( m_lpszPayload );
    }
}

STDMETHODIMP CPushNotification::Init( DWORD dwExpiration, BYTE bPriority, const DeviceToken& deviceToken, LPCSTR lpszPayload )
{
    if( !lpszPayload || strlen(lpszPayload) > 256 )
        return E_INVALIDARG;

    m_dwID         = ++m_dwID;
    m_dwExpiration = dwExpiration;
    m_bPriority    = bPriority;
    memcpy( &m_Token, &deviceToken, sizeof(DeviceToken) );
    m_lpszPayload  = _strdup( lpszPayload );
    return S_OK;
}

STDMETHODIMP CPushNotification::SetPayload( LPCSTR lpszPayload )
{
    if( !lpszPayload )
        return E_POINTER;
    if( strlen( lpszPayload ) == 0 )
        return E_INVALIDARG;

    if( m_lpszPayload )
    {
        free( m_lpszPayload );
        m_lpszPayload = NULL;
    }

    m_lpszPayload = _strdup( lpszPayload );
    return S_OK;
}

STDMETHODIMP CPushNotification::Makeup( DWORD dwType, IGMSBuffer **ppOut )
{
    if( m_dwID == 0 )
        return E_UNEXPECTED;
    if( !ppOut )
        return E_POINTER;
    if( dwType > 2 )
        return E_INVALIDARG;

    CComPtr<IGMSBuffer> spBuf;
    HRESULT hr = CreateGMSBuffer( &spBuf );
    if( SUCCEEDED( hr ) )
    {
        switch( dwType )
        {
        case 0: hr = PrepareNtfFormat0( spBuf ); break;
        case 1: hr = PrepareNtfFormat1( spBuf ); break;
        case 2:
        default:hr = PrepareNtfFormat2( spBuf ); break;
        }
    }

    if( SUCCEEDED( hr ) )
    {
        *ppOut = spBuf.Detach();
    }

    return hr;
}

LPBYTE CPushNotification::storeBYTE( LPBYTE p, BYTE dwValue )
{
    *p++ = dwValue;
    return p;
}

LPBYTE CPushNotification::storeWORD( LPBYTE p, WORD dwValue )
{
    *((WORD*)p) = htons( dwValue );
    p += sizeof(WORD);
    return p;
}

LPBYTE CPushNotification::storeDWORD( LPBYTE p, DWORD dwValue )
{
    *((DWORD*)p) = htonl( dwValue );
    p += sizeof(DWORD);
    return p;
}

LPBYTE CPushNotification::storeBLOCK( LPBYTE p, LPBYTE pValue, DWORD dwSize )
{
    memcpy( p, pValue, dwSize ); p += dwSize;
    return p;
}

LPBYTE CPushNotification::storeSTRING( LPBYTE p, LPCSTR lpszValue )
{
    strcpy( (char*)p, lpszValue ); p += strlen( lpszValue );
    return p;
}

HRESULT CPushNotification::PrepareNtfFormat0( IGMSBuffer *pBuf )
{
    HRESULT hr = E_FAIL;

    BYTE buf[512];
    ZeroMemory( buf, 512 );

    LPBYTE pPtr = &buf[0];

    // device token
    pPtr = storeWORD( pPtr, 32 );
    pPtr = storeBLOCK( pPtr, (LPBYTE)&m_Token.tok[0], 32 );

    // payload
    WORD wLen = strlen( m_lpszPayload );
    pPtr = storeWORD( pPtr, wLen );
    pPtr = storeBLOCK( pPtr, (LPBYTE)m_lpszPayload, wLen );

    hr  = pBuf->AddBYTE( 0 ); // CMD of format 0
    hr |= pBuf->Add( &buf[0], pPtr - buf );

    if( FAILED( hr ) )
        return E_OUTOFMEMORY;

    return hr;
}

HRESULT CPushNotification::PrepareNtfFormat1( IGMSBuffer *pBuf )
{
    HRESULT hr = E_FAIL;

    BYTE buf[512];
    ZeroMemory( buf, 512 );

    LPBYTE pPtr = &buf[0];

    // ID
    pPtr = storeWORD( pPtr, sizeof(DWORD) );
    pPtr = storeDWORD( pPtr, m_dwID );

    // Exp date
    pPtr = storeWORD( pPtr, sizeof(DWORD) ); 
    pPtr = storeDWORD( pPtr, 0 ); // deliver immediately

    // device token
    pPtr = storeWORD( pPtr, 32 );
    pPtr = storeBLOCK( pPtr, (LPBYTE)&m_Token.tok[0], 32 );

    // payload
    WORD wLen = strlen( m_lpszPayload );
    pPtr = storeWORD( pPtr, wLen );
    pPtr = storeBLOCK( pPtr, (LPBYTE)m_lpszPayload, wLen );

    hr  = pBuf->AddBYTE( 0 ); // CMD of format 0
    hr |= pBuf->Add( &buf[0], pPtr - buf );

    if( FAILED( hr ) )
        return E_OUTOFMEMORY;

    return hr;
}

HRESULT CPushNotification::PrepareNtfFormat2( IGMSBuffer *pBuf )
{
    HRESULT hr = E_FAIL;

    BYTE buf[512];
    ZeroMemory( buf, 512 );

    LPBYTE pPtr = &buf[0];

    // device token
    pPtr = storeBYTE( pPtr, 1 );
    pPtr = storeWORD( pPtr, 32 );
    pPtr = storeBLOCK( pPtr, (LPBYTE)&m_Token.tok[0], 32 );

    // payload
    WORD wLen = strlen( m_lpszPayload );
    pPtr = storeBYTE( pPtr, 2 );
    pPtr = storeWORD( pPtr, wLen );
    pPtr = storeBLOCK( pPtr, (LPBYTE)m_lpszPayload, wLen );

    // ID
    pPtr = storeBYTE( pPtr, 3 );
    pPtr = storeWORD( pPtr, sizeof(DWORD) );
    pPtr = storeDWORD( pPtr, m_dwID );
    
    // Exp date
    pPtr = storeBYTE( pPtr, 4 );
    pPtr = storeWORD( pPtr, sizeof(DWORD) ); 
    pPtr = storeDWORD( pPtr, 0 ); // deliver immediately

    // Priority
    pPtr = storeBYTE( pPtr, 5 );
    pPtr = storeWORD( pPtr, sizeof(WORD) ); 
    pPtr = storeBYTE( pPtr, m_bPriority );
 
    DWORD dwSize = pPtr - buf;
    DWORD dwSizeNet = htonl( dwSize );
    
    hr  = pBuf->AddBYTE( 2 );                     // CMD of new format
    hr |= pBuf->Add( (LPBYTE)&dwSizeNet, sizeof(DWORD) ); // frame size
    hr |= pBuf->Add( &buf[0], dwSize );

    if( FAILED( hr ) )
        hr = E_OUTOFMEMORY;
    
    return hr;
}